package com.bytenose.extrautils.collect;

import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import com.bytenose.extrautils.annotations.NonNullable;
import com.bytenose.extrautils.annotations.UtilityClass;
import com.bytenose.extrautils.base.Functions2;
import com.bytenose.extrautils.functional.MutableTuple2;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.Iterables;

/**
 * Defines useful methods for maps.
 * @author Mauro Luigi Drago
 *
 */
@UtilityClass
public final class Maps2 {
	
	/**
	 * Private constructor to avoid instantiation.
	 */
	private Maps2() {
	}

	/**
	 * Transforms the entries of a map by applying two user supplied
	 * functions to keys and to values.
	 * This method returns an {@link Iterable} of {@link MutableTuple2}
	 * and not a map, since nothing guarantees that the keys will be unique
	 * after applying the {@code keyTransformer} function.
	 * The returned iterable is a non-materialized iterable yielding mutable tuples.
	 * To improve performance, always the same tuple object is returned
	 * over subsequent invocations of the {@link Iterator#next()} with
	 * tuple values reflecting the currently scanned elements.
	 * This avoid creating a lot of unnecessary objects, but limits the 
	 * applicability of the method.
	 * If values must be cached across different invocations of the 
	 * {@link Iterator#next()} method, first materialize the iterable
	 * or the tuple.
	 * 
	 * The returned iterable and its associated iterator do not support the 
	 * {@link Iterator#remove()} functionality.
	 * 
	 * @param <K1> the type of the keys of the source map. 
	 * @param <K2> the type of the keys for the transformed entries.
	 * @param <V1> the type of the values of the source map.
	 * @param <V2> the type of the values for the transformed entries.
	 * @param fromMap the source map, cannot be null.
	 * @param keyTransformer the function that transforms keys, cannot be null.
	 * @param valueTransformer the function that transforms values, cannot be null.
	 * 
	 * @return
	 * 		the iterable of the transformed entries.
	 * 
	 * @throws IllegalArgumentException
	 * 		if some of the non nullable arguments are null.
	 */
	@NonNullable
	public static <K1, K2, V1, V2> Iterable<MutableTuple2<K2, V2>> transformEntries(
			@NonNullable Map<K1, V1> fromMap, @NonNullable final Function<K1,K2> keyTransformer,
			@NonNullable final Function<V1,V2> valueTransformer) 
			throws IllegalArgumentException {
		if (fromMap == null) throw new IllegalArgumentException("fromMap");
		if (keyTransformer == null) throw new IllegalArgumentException("keyTransformer");
		if (valueTransformer == null) throw new IllegalArgumentException("valueTransformer");
		
		return Iterables.unmodifiableIterable(Iterables.transform(
				fromMap.entrySet(), 
				new Function<Entry<K1,V1>, MutableTuple2<K2, V2>>() {
					private final MutableTuple2<K2,V2> sharedTuple =
						new MutableTuple2<K2, V2>(null, null);
					
					@Override
					public MutableTuple2<K2, V2> apply(Entry<K1, V1> input) {
						sharedTuple.setValue1(keyTransformer.apply(input.getKey()));
						sharedTuple.setValue2(valueTransformer.apply(input.getValue()));
						return sharedTuple;
					}
				}));
	}
	
	/**
	 * Transforms the entries of a map by applying the {@link Functions2#toStringFunction()}
	 * function to map keys, and the {@link Functions#identity()} function to the values.
	 * This method then behaves as {@link Maps2#transformEntries(Map, Function, Function)}.
	 * @param <K> the type of the keys of the source map.
	 * @param <V> the type of the values of the source map.
	 * @param fromMap the source map, cannot be null.
	 * 
	 * @return
	 * 		the iterable of the transformed entries.
	 * 
	 * @throws IllegalArgumentException
	 * 		if some of the non nullable arguments are null.
	 */
	@NonNullable
	public static <K, V> Iterable<MutableTuple2<String,V>> transformKeysToStrings(
			@NonNullable Map<K,V> fromMap) throws IllegalArgumentException {
		return transformEntries(fromMap, Functions2.<K>toStringFunction(), Functions.<V>identity());
	}
}
